﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Collections;
using System.Windows.Forms;
using Midi;

namespace LaserHarp
{

    public partial class LaserHarp : Form
    {
        /*
        Dataformat Frame
A laserframe for output is located in a dataarray and has 8 bytes per point:
X 2 Bytes 0 - 4095 //X-coordinate
Y 2 Bytes 0 - 4095 //Y-coordinate
R 1 Byte 0 - 255 //Red
G 1 Byte 0 - 255 //Green
B 1 Byte 0 - 255 //Blue
I 1 Byte 0 - 255 //Intensity
*/
        #region "Private variables"

        private byte[,] FrameArray = new byte[400, 8];
        private int PointCount = 0;
        private int FrameCount = 0;
        private int EasyLaseCardNumber;
        private byte lum;
        private int calibrate = 0;
        private int notecount = 0;
        private int lastX = 0;
        private int posY1;
        private int posY2;
        private int Ytotal;

        private ImageProcessing imgProc = new ImageProcessing();

        private MidiOutPort midiPort;

        private int deviceID = 0;

        private LEDarray strings;
        private ComboArray notes;
        private TextBoxArray xpos1;
        private TextBoxArray xpos2;

        private BindingList<BeamConfig> configs;
        private ConfigManager mgr = new ConfigManager();

        private List<Beam> beams = new List<Beam>();

        #endregion

        public LaserHarp()
        {
            InitializeComponent();
        }

        private void BuildFrame()
        {
            PointCount = 0;
            float scale = (float)tbXscale.Value / 100;
            int Xoffset = (int)(2048 * (1 - scale));
            int Xstep = (int)(4096 / (float)udBeams.Value * scale);
            int Yoffset = (int)(4096 * (100 - tbYoffset.Value) / 100);

            Console.WriteLine("X Offset = {0}, X step = {1}", Xoffset, Xstep);

            // Do flyback blanking
            for (int i = 0; i < udFlyback.Value; i++)
            {
                lum = 0; //blank beam
                AddDataPoint(Xoffset, Yoffset, lum);
            }

            for (int i = 0; i < udBeams.Value; i++)
            {
                int tilt = tbTilt.Value * i;
                lum = 0; //blank beam
                for (int j = 0; j < udDwell.Value; j++)
                {
                    if (j > udBlankCount.Value) lum = 255; //unblank beam
                    AddDataPoint(Xoffset + Xstep * i, Yoffset + tilt, lum);
                }
            }

            // Wait for interface to finish sending last frame
            while (EasyLase.EasyLaseGetStatus(ref EasyLaseCardNumber) != 1)
            {
                Application.DoEvents();   //wait until free
            }

            int speed = Convert.ToInt16(txtSpeed.Text);
            txtPoints.Text = PointCount.ToString();
            txtFrames.Text = Convert.ToString(speed / PointCount);
            int result = EasyLase.EasyLaseWriteFrame(ref EasyLaseCardNumber, FrameArray, PointCount * 8, speed);

            if (result == 0) MessageBox.Show("Error in sending frame");
        }

        private void AddDataPoint(long Xpos, long Ypos, byte intensity)
        {
            FrameArray[PointCount, 0] = (byte)(Xpos % 256);
            FrameArray[PointCount, 1] = (byte)(Xpos / 256);
            FrameArray[PointCount, 2] = (byte)(Ypos % 256);
            FrameArray[PointCount, 3] = (byte)(Ypos / 256);
            FrameArray[PointCount, 4] = 0; //Red
            FrameArray[PointCount, 5] = intensity; //Green
            FrameArray[PointCount, 6] = 0; //Blue
            FrameArray[PointCount, 7] = intensity; //Intensity

            //Console.WriteLine("Point: {0}., X= {1}, lum= {2}", PointCount, FrameArray[PointCount, 1], intensity);
            PointCount++;
        }

        private void ControlChange(object sender, EventArgs e)
        {
            if (EasyLase.EasyLaseGetStatus(ref EasyLaseCardNumber) == 1)
                BuildFrame();
            //txtFrameRate.Text = tbFrameRate.Value.ToString();
            // timer1.Interval = 1000 / tbFrameRate.Value;
        }

        private void ProcessFrame(object sender, EventArgs arg)
        {
            imageBox1.Image = imgProc.returnImage();
            imageBox2.Image = imgProc.returnThresholdedImage();
            List<BitBucket> beams = imgProc.returnBlobs(tbFeatures.Value);
            if (calibrate > 0) CalibrateBeams(beams);
            else ProcessNotes(beams);

            FrameCount++;
            txtPoints2.Text = beams.Count.ToString();
        }

        private void ProcessNotes(List<BitBucket> blobs)
        {
            // Iterate through all beams to see if any are triggered
            for (int i = 0; i < beams.Count; i++)
            {
                bool beamon = false;

                //Iterate though all detected blobs
                foreach (BitBucket blob in blobs)
                {
                    if (beams[i].IsNear(blob))
                    {
                        beams[i].status++;
                        if (beams[i].status == 1) //note on
                        {
                            // New note found, so send note-on message to MIDI out port
                            SendMidi(beams[i], 0x90);
                            strings[i].BackColor = Color.Lime;
                        }
                        else beams[i].status = 2;

                        //Process aftertouch
                        byte aftertouch = beams[i].Aftertouch(blob);
                        SendMidi(aftertouch, 0xD0);
                        txtAftertouch.Text = aftertouch.ToString();

                        beamon = true;
                        break;
                    }
                }

                if (!beamon)
                {
                    //Decrement beam cycle counter
                    if (beams[i].status > 0)
                    {
                        Console.WriteLine("Beam {0} status = {1}", i, beams[i].status);
                        beams[i].status--;
                        if (beams[i].status <= 0)
                        {
                            //Turn off note
                            beams[i].status = 0;
                            SendMidi(beams[i], 0x80);
                            strings[i].BackColor = Color.DarkGreen;
                        }
                    }
                }
            }
        }

        private void CalibrateBeams(List<BitBucket> blobs)
        {
            if (blobs.Count > 0 && blobs[0].avgX > lastX + 10)
            {
                Ytotal += blobs[0].avgY;
                if (calibrate == 1)
                {
                    Beam beam = new Beam();
                    lastX = blobs[0].avgX;
                    xpos1[notecount].Text = lastX.ToString();
                    beam.note = (byte)notes[notecount].SelectedIndex;
                    notecount++;
                    beam.X1 = lastX;
                    beam.Y1 = Ytotal / notecount;
                    txtY1.Text = beam.Y1.ToString();
                    beams.Add(beam);
                }
                else if (blobs[0].avgY > posY1 + 30)
                {
                    Beam beam = beams[notecount];
                    lastX = blobs[0].avgX;
                    xpos2[notecount].Text = lastX.ToString();
                    notecount++;
                    beam.X2 = lastX;
                    beam.Y2 = Ytotal / notecount;
                    beam.slope = (beam.X2 - beam.X1) / (beam.Y2 - beam.Y1);
                    txtY2.Text = beam.Y2.ToString();
                }
            }
            else
                //Check whether all beams have been calibrated
                if (notecount >= udBeams.Value)
                {
                    calibrate++;
                    if (calibrate == 2)
                    {
                        txtY1.BackColor = Color.White;
                        txtY2.BackColor = Color.Red;
                        posY1 = Ytotal / notecount;
                        lastX = 0;
                        Ytotal = 0;
                        notecount = 0;
                    }
                    else if (calibrate > 2)
                    {
                        calibrate = 0;
                        posY2 = Ytotal / notecount;
                        cmdCalibrate.Enabled = true;
                        txtY2.BackColor = Color.White;
                        BeamConfig cfg = (BeamConfig)lstConfigs.SelectedItem;
                        configs.Remove(cfg);
                        AddConfig(cfg);
                    }
                }
        }

        private void ButtonClicked(object sender, EventArgs e)
        {
            Button but = (Button)sender;
            but.BackColor = Color.LightGreen;
            but.Refresh();
            Application.DoEvents();
            int beam = (int)but.Tag;
            SendMidi(beams[beam], 0x90);
            DateTime start = DateTime.Now.AddSeconds(2);
            while (start > DateTime.Now)
            {
                //Wait
            }
            // Send note off
            SendMidi(beams[beam], 0x80);
            but.BackColor = Color.DarkGreen;
        }

        private void NoteChanged(object sender, EventArgs e)
        {
            ComboBox cbo = (ComboBox)sender;
            int beam = (int)cbo.Tag;
            beams[beam].note = (byte)cbo.SelectedIndex;
        }

        private void SendMidi(Beam beam, Byte status)
        {
            midiPort.SendMsg(status, beam.note, 0x7F);
        }

        private void SendMidi(byte value, Byte status)
        {
            midiPort.SendMsg(status, value);
        }

        private void AddConfig(BeamConfig cfg)
        {
            cfg.ConfigName = txtConfigName.Text;
            cfg.nBeams = (int)udBeams.Value;
            cfg.midiOutID = cboDevices.SelectedIndex;
            cfg.Y1 = posY1;
            cfg.Y2 = posY2;
            cfg.notes = new int[cfg.nBeams];
            cfg.posX1 = new int[cfg.nBeams];
            cfg.posX2 = new int[cfg.nBeams];
            for (int i = 0; i < cfg.nBeams; i++)
            {
                cfg.notes[i] = notes[i].SelectedIndex;
                cfg.posX1[i] = beams[i].X1;
                cfg.posX2[i] = beams[i].X2;
            }

            //Add config to list box
            configs.Add(cfg);
            lstConfigs.Refresh();
        }

        #region "Form Control Events"

        private void Form1_Load(object sender, EventArgs e)
        {
            EasyLaseCardNumber = EasyLase.EasyLaseGetCardNum() - 1;
            if (EasyLaseCardNumber < 0) MessageBox.Show("Easylase interface not found");
            else
            {
                txtSpeed.Text = Convert.ToString(1000 * tbSpeed.Value);

                //Add event handler for control changes
                udBeams.ValueChanged += new EventHandler(ControlChange);
                udDwell.ValueChanged += new EventHandler(ControlChange);
                udBlankCount.ValueChanged += new EventHandler(ControlChange);
                udFlyback.ValueChanged += new EventHandler(ControlChange);
                tbSpeed.ValueChanged += new EventHandler(tbSpeed_Scroll);
                tbXscale.ValueChanged += new EventHandler(ControlChange);
                tbYoffset.ValueChanged += new EventHandler(ControlChange);
                tbFrameRate.ValueChanged += new EventHandler(ControlChange);
                tbTilt.ValueChanged += new EventHandler(ControlChange);
                this.FormClosing += new FormClosingEventHandler(Form1_FormClosing);

                BuildFrame();
            }

            // Add string LEDs
            strings = new LEDarray(gpStrings);
            strings.SendNote += new NoteSentHandler(ButtonClicked);
            notes = new ComboArray(gpStrings);
            notes.ChangeNote += new NoteChangedHandler(NoteChanged);

            xpos1 = new TextBoxArray(gpStrings, 70);
            xpos2 = new TextBoxArray(gpStrings, 100);

            for (int i = 0; i < 8; i++)
            {
                Button btn = strings.AddNewButton();
                btn.BackColor = Color.DarkGreen;

                ComboBox cbo = notes.AddNewCombo();
                //cbo.SelectedIndex = 60 + i;

                TextBox txtX1 = xpos1.AddNewTextBox();
                TextBox txtX2 = xpos2.AddNewTextBox();
            }


            //Turn on frame update timer
            timer1.Interval = 1000;
            timer1.Enabled = true;

            Application.Idle += ProcessFrame;

            //Open MIDI out port
            midiPort = new MidiOutPort();
            int devcount = midiPort.GetNumDevs();
            for (int i = 0; i < devcount; i++)
            {
                MidiOutCaps caps = midiPort.GetDeviceCapabilities(i);
                cboDevices.Items.Add(caps.name);
            }

            midiPort.Open(deviceID);

            //Set up beam configs
            configs = new BindingList<BeamConfig>();
            lstConfigs.DataSource = configs;
            lstConfigs.DisplayMember = "ConfigName";

            //Do camera settings 
            imgProc.setCameraParameters(0, 0, 0, gainBar.Value, exposureBar.Value, 50, 50, 50);
            txtThreshold.Text = tbThreshold.Value.ToString();
            imgProc.threshold = tbThreshold.Value;
            txtFeatureThreshold.Text = tbFeatures.Value.ToString();
        }

        private void cmdClose_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void Form1_FormClosing(object sender, EventArgs e)
        {
            EasyLase.EasyLaseClose();
        }

        private void cameraParameter_Changed(object sender, EventArgs e)
        {
            imgProc.setCameraParameters(0, 0, 0, gainBar.Value, exposureBar.Value, 50, 50, 50);
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            txtFrameRate.Text = FrameCount.ToString();
            FrameCount = 0;
        }

        private void tbThreshold_Scroll(object sender, EventArgs e)
        {
            txtThreshold.Text = tbThreshold.Value.ToString();
            imgProc.threshold = tbThreshold.Value;
        }

        private void tbSpeed_Scroll(object sender, EventArgs e)
        {
            txtSpeed.Text = Convert.ToString(1000 * tbSpeed.Value);
            BuildFrame();
        }

        private void tbFeatures_Scroll(object sender, EventArgs e)
        {
            txtFeatureThreshold.Text = tbFeatures.Value.ToString();
        }

        private void cboDevices_SelectedIndexChanged(object sender, EventArgs e)
        {
            midiPort.Close();
            deviceID = cboDevices.SelectedIndex;
            midiPort.Open(deviceID);
        }

        private void cmdCalibrate_Click(object sender, EventArgs e)
        {
            notecount = 0;
            lastX = 0;
            Ytotal = 0;
            calibrate = 1;
            cmdCalibrate.Enabled = false;
            txtY1.Text = "";
            txtY2.Text = "";
            txtY1.BackColor = Color.Red;
            beams.Clear();
            for (int i = 0; i < udBeams.Value; i++)
            {
                xpos1[i].Text = "";
                xpos2[i].Text = "";
            }
        }

        private void cmdSave_Click(object sender, EventArgs e)
        {
            // Save config as XML file
            mgr.SaveConfig(configs);
        }

        private void cmdLoad_Click(object sender, EventArgs e)
        {
            // Load config from XML file
            configs = mgr.LoadConfig();
            lstConfigs.DataSource = configs;
            lstConfigs.DisplayMember = "ConfigName";
        }

        private void cmdReset_Click(object sender, EventArgs e)
        {
            calibrate = 0;
            cmdCalibrate.Enabled = true;
        }

        private void lstConfigs_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (lstConfigs.SelectedItem != null)
            {
                BeamConfig cfg = (BeamConfig)lstConfigs.SelectedItem;
                udBeams.Value = cfg.nBeams;
                cboDevices.SelectedIndex = cfg.midiOutID;
                posY1 = cfg.Y1;
                posY2 = cfg.Y2;
                txtY1.Text = posY1.ToString();
                txtY2.Text = posY2.ToString();
                txtConfigName.Text = cfg.ConfigName;
                beams.Clear();
                for (int i = 0; i < cfg.nBeams; i++)
                {
                    Beam beam = new Beam();
                    beam.index = i;
                    beam.note = (byte)cfg.notes[i];
                    beam.X1 = cfg.posX1[i];
                    beam.X2 = cfg.posX2[i];
                    beam.Y1 = cfg.Y1;
                    beam.Y2 = cfg.Y2;
                    beam.status = 0;
                    beam.slope = (beam.X2 - beam.X1) / (beam.Y2 - beam.Y1);
                    beams.Add(beam);
                    notes[i].SelectedIndex = cfg.notes[i];
                    xpos1[i].Text = beams[i].X1.ToString();
                    xpos2[i].Text = beams[i].X2.ToString();
                }
            }
        }

        private void cmdAdd_Click(object sender, EventArgs e)
        {
            BeamConfig cfg = new BeamConfig();
            cfg.ConfigName = "My Config " + configs.Count.ToString();
            AddConfig(cfg);
        }

        private void txtConfigName_TextChanged(object sender, EventArgs e)
        {
            BeamConfig cfg = (BeamConfig)lstConfigs.SelectedItem;
            cfg.ConfigName = txtConfigName.Text;
            lstConfigs.SelectedItem = cfg;
        }

        private void cmdDelete_Click(object sender, EventArgs e)
        {
            BeamConfig cfg = (BeamConfig)lstConfigs.SelectedItem;
            configs.Remove(cfg);
        }
       
        #endregion
    
    }
}
